import crypto from 'crypto'
import axios from 'axios'

/** 枚举：支付方式 */
export enum EPayType {
  /** 支付宝 */
  Alipay = 'alipay',
  /** 微信 */
  Wxpay = 'wxpay',
  /** QQ钱包 */
  QQpay = 'qqpay',
  /** 财付通 */
  Tenpay = 'tenpay',
}

/** 类型：支付配置 */
interface EPaySDKConfig {
  /** pid */
  appId: string
  /** key */
  secretKey: string
  /** 网关地址 默认https://pay.tianmuzf.top */
  gatewayUrl?: string
  /** 签名类型 暂时只有MD5 */
  signType?: 'MD5'
}

/** 类型：业务请求参数 */
interface EPayBizContent {
  /** 支付类型 默认alipay */
  type?: EPayType
  /** 订单号 */
  out_trade_no: string
  /** 金额 */
  money: number
  /** 商品名称 */
  name: string
  /** 网站名称 */
  sitename?: string
}

/** 类型：生成支付格式 */
export enum EPayPageExecuteResFormat {
  /** url */
  Url = 1,
  /** 二维码 */
  QRCode = 2,
}

/** 类型：发起支付请求参数 */
interface EPayPageExecuteParams {
  /** 业务请求参数 */
  bizContent: EPayBizContent
  /** 页面跳转通知地址 */
  return_url: string
  /** 异步通知地址 */
  notify_url: string
}

/** 类型：通用结果 */
interface EPayCommonResult {
  /** 响应状态 1为成功，其它值为失败 */
  code: number
  /** 响应信息 */
  msg: string
}

/** 类型：商户信息 */
interface EPayMerchantResult extends EPayCommonResult {
  /** 商户id */
  pid: number
  /** 商户秘钥 */
  key: string
  /** 商户状态 1正常 0封禁 */
  active: string
  /** 近两日订单数 */
  orders: string
  /** 今日收入 */
  order_today: string
  /** 以往收入 */
  order_lastday: string
}

/** 类型：订单信息 */
interface EPayOrderResult {
  /** 订单号 */
  out_trade_no: string
  /** 流水号 */
  trade_no: string
  /** 支付方式 */
  type: EPayType
  /** 商户id */
  pid: number
  /** 订单创建时间 */
  addtime: string
  /** 完成交易时间 */
  endtime: string
  /** 商品名称 */
  name: string
  /** 商品金额 */
  money: string
  /** 订单状态 1为成功，0/2为未支付 */
  status: number
}
/** 类型：单个订单结果 */
export interface EPaySignalOrder extends EPayCommonResult, EPayOrderResult {}

/** 类型：批量订单结果 */
export interface EPayBatchOrder extends EPayCommonResult {
  data: EPayOrderResult[]
}

export class EPaySDK {
  public config: EPaySDKConfig

  /**
   * @param {EPaySDKConfig} config 初始化配置
   */
  constructor(config: EPaySDKConfig) {
    if (!config.appId) {
      throw Error('config.appId is required')
    }
    if (!config.secretKey) {
      throw Error('config.secretKey is required')
    }
    this.config = {
      signType: 'MD5',
      ...config,
      gatewayUrl: !config.gatewayUrl
        ? 'https://pay.tianmuzf.top'
        : config.gatewayUrl!.endsWith('/')
          ? config.gatewayUrl!.slice(0, -1)
          : config.gatewayUrl,
    }
  }

  /**
   * 生成支付链接
   * @param {EPayPageExecuteParams} params 请求参数
   * @returns 支付链接/base64支付二维码Obj
   */
  public async pageExecute(resFormat: EPayPageExecuteResFormat, params: EPayPageExecuteParams) {
    try {
      const { appId, secretKey, gatewayUrl, signType } = this.config
      const {
        bizContent: { type = EPayType.Alipay, out_trade_no, money, name, sitename = '' },
        return_url,
        notify_url,
      } = params
      const obj = {
        type,
        out_trade_no,
        name,
        return_url,
        notify_url,
        sitename,
        money,
        pid: appId,
      }
      const queryStr = this.toQueryString(this.objKeySort(obj))
      let sign = ''
      if (signType === 'MD5') {
        sign = crypto
          .createHash('md5')
          .update(queryStr + secretKey)
          .digest('hex')
      }
      const url = `${gatewayUrl}/submit.php?${queryStr}&sign=${sign}&sign_type=${signType}`

      const hrefRes = await axios.get(url)
      const hrefMatch = hrefRes.data?.match(/window\.location\.href='(.*)'/)
      const href = hrefMatch ? hrefMatch[1] : ''
      if (!href) {
        const msgMatch = hrefRes.data?.match(/<\/h3>(.*)<\/body>/)
        return {
          code: -1,
          msg: msgMatch ? msgMatch[1]?.trim() : '未知错误',
        }
      }
      // 支付跳转地址
      const jumpUrl = `${gatewayUrl}/${href}`
      if (resFormat === EPayPageExecuteResFormat.Url) {
        return { code: 200, msg: '成功', payUrl: jumpUrl }
      } else {
        const tradeNoMatch = href?.match(/trade_no=([\w]+)/)
        // 流水号
        const tradeNo = tradeNoMatch ? tradeNoMatch[1] : ''
        // 获取静态页面(用于提取二维码base64)和状态接口的money(金额有时候会被其他用户金额占用 会多1分钱，得查一次接口才知道实际多少钱)
        const [htmlRes, notifyRes] = await Promise.all([
          axios.get(jumpUrl),
          axios.get(jumpUrl.replace('Mcode_Pay', 'Mcode_Get')),
        ])
        const qrcodeMatch = htmlRes.data?.match(/<img id="qrcode_load" src="data(.*)">/)
        const qrcode = qrcodeMatch ? 'data' + qrcodeMatch[1] : ''
        return { code: 200, msg: '成功', tradeNo, money: notifyRes.data.data?.money, qrcode }
      }
    } catch (error) {
      throw new Error('e支付请求失败' + (error as Error).name + '：' + (error as Error).message)
    }
  }

  /**
   * 支付结果验证
   * @param {object} notifyData 通知参数
   */
  public checkNotifySign(notifyData: Record<string, any>) {
    const { secretKey, signType } = this.config
    const queryStr = this.toQueryString(this.objKeySort(notifyData))
    let sign = ''
    if (signType === 'MD5') {
      sign = crypto
        .createHash('md5')
        .update(queryStr + secretKey)
        .digest('hex')
    }
    return sign === notifyData.sign
  }

  /**
   * 查询订单信息
   * @param {string | null} outTradeNo 订单号,不传时默认查询所有
   * @param {number} limit 订单数量(批量时生效) 默认20
   */
  public async queryOrder(
    outTradeNo: string | null,
    limit: number = 20,
  ): Promise<EPaySignalOrder | EPayBatchOrder> {
    try {
      const { gatewayUrl, appId, secretKey } = this.config
      const res = await axios.get(`${gatewayUrl}/api.php`, {
        params: {
          act: outTradeNo ? 'order' : 'orders',
          pid: appId,
          key: secretKey,
          out_trade_no: outTradeNo,
          limit,
        },
      })
      return res.data
    } catch (error) {
      throw new Error('e支付请求失败' + (error as Error).name + '：' + (error as Error).message)
    }
  }

  /**
   * 对象key值排序
   * @param {object} obj 待排序对象
   * @param {boolean} asc 是否升序 默认升序
   * @returns newObj
   */
  private objKeySort(obj: Record<string, any>, asc: boolean = true) {
    const newKey = Object.keys(obj).sort(
      asc ? (a, b) => a.localeCompare(b) : (a, b) => b.localeCompare(a),
    )
    const newObj = {} as Record<string, any>
    for (const i of newKey) {
      newObj[i] = obj[i]
    }
    return newObj
  }

  /**
   * 将对象拼接为查询字符串(过滤掉sign和sign_type、排除空值)
   * @param {object} obj
   * @returns queryString
   */
  private toQueryString(obj: Record<string, any>): string {
    const queryString = Object.entries(obj)
      .filter(([key, value]) => !['sign', 'sign_type'].includes(key) && value !== '')
      .map(([key, value]) => `${key}=${value}`)
      .join('&')
    return queryString
  }
}

export const ePaySDK = new EPaySDK({
  appId: process.env.EZF_APPID as string,
  secretKey: process.env.EZF_SECRET_KEY as string,
})
